/*
 * Copyright 2014 Jonathan Haas
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package app.grinreader.library;

import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.database.DataSetObservable;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import app.grinreader.R;
import app.grinreader.ui.ReadActivity;
import nl.siegmann.epublib.domain.Author;
import nl.siegmann.epublib.domain.Book;
import nl.siegmann.epublib.domain.Relator;
import nl.siegmann.epublib.epub.EpubReader;
import nl.siegmann.epublib.epub.EpubWriter;

import java.io.*;
import java.util.*;


public class Library extends DataSetObservable {

    private Context context;
    private Map<String, BookData> cache = new HashMap<String, BookData>();

    public Library(Context context) {
        this.context = context;
    }

    public void importBook(Uri uri) throws IOException {
        InputStream inputStream;
        if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) {
            DownloadManager.Request request = new DownloadManager.Request(uri);
            request.setTitle(context.getString(R.string.download_progress_title));
            request.setDescription(uri.getLastPathSegment());
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, uri.getLastPathSegment());
            DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
            manager.enqueue(request);
        } else if (uri.getScheme().equals("file")
                && new File(uri.getPath()).getCanonicalPath().startsWith(getBooksDirectory().getCanonicalPath())) {
            startReadIntent(new File(uri.getPath()));
        } else {
            inputStream = context.getContentResolver().openInputStream(uri);
            Book book = new EpubReader().readEpub(inputStream);
            EpubWriter epubWriter = new EpubWriter();
            File outFile = new File(getBooksDirectory(), getBookFileName(book));
            epubWriter.write(book, new FileOutputStream(outFile));
            startReadIntent(outFile);
        }
    }

    private void startReadIntent(File outFile) {
        Intent intent = new Intent(context, ReadActivity.class);
        intent.putExtra(ReadActivity.EXTRA_BOOK_FILE, outFile.getAbsolutePath());
        context.startActivity(intent);
    }

    private String getBookFileName(Book book) {
        StringBuilder stringBuilder = new StringBuilder();
        for (Author author : book.getMetadata().getAuthors()) {
            if (author.getRelator() == Relator.AUTHOR) {
                stringBuilder.append(author);
                stringBuilder.append(" ");
                break;
            }
        }
        stringBuilder.append(book.getTitle());
        stringBuilder.append(".epub");
        return stringBuilder.toString();
    }

    private File getBooksDirectory() {
        File books = new File(Environment.getExternalStorageDirectory(), "Books");
        if (!books.exists()) {
            books.mkdirs();
        }
        return books;
    }

    public List<String> getBooks() {
        File[] files = getBooksDirectory().listFiles(new FilenameFilter() {
            public boolean accept(File dir, String filename) {
                return filename.endsWith(".epub");
            }
        });
        if(files == null) return Collections.emptyList();
        final List<String> bookArrayList = new ArrayList<String>(files.length);
        for (File file : files) {
            bookArrayList.add(file.getAbsolutePath());
        }
        return bookArrayList;
    }

    public synchronized BookData getBookData(String bookFile) {
        BookData bookData = cache.get(bookFile);
        if(bookData == null){
            bookData = readBookData(bookFile);
        }
        if(bookData == null){
            createCacheEntryAsync(bookFile);
        }
        if(bookData == null){
            String name = new File(bookFile).getName().replaceAll("\\.epub$","");
            bookData = new BookData(name, Collections.<Author>emptyList(), null);
            bookData.setDummy(true);
        }
        cache.put(bookFile, bookData);
        return bookData;
    }

    private BookData readBookData(String bookFile) {
        File cacheDir = getDataDir();
        File cacheFile = new File(cacheDir, bookFile.replace(File.separatorChar, '_') + ".cache");
        File bitmapFile = new File(cacheDir, bookFile.replace(File.separatorChar, '_') + ".cover");
        if(!cacheFile.exists()){
            return null;
        }
        ObjectInputStream inputStream = null;
        try {
            inputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(cacheFile)));
        } catch (IOException e) {
            return null;
        }
        BookData bookData = null;
        try{
            Object o = inputStream.readObject();
            if (o instanceof BookData) {
                bookData = (BookData) o;
                if(bitmapFile.exists()){
                    Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile));
                    bookData.setThumbnail(bitmap);
                }
            }
        }
        catch (Exception e){
            e.printStackTrace();
            return null;
        }
        finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bookData;
    }

    private File getDataDir() {
        File dataDir = new File(getBooksDirectory(), ".bookdata");
        if (!dataDir.exists()) {
            dataDir.mkdirs();
        }
        if(dataDir.exists()){
            File file = new File(dataDir, ".nomedia");
            try {
                file.createNewFile();
            } catch (IOException e) {
                Log.e("reader","could not create nomedia file",e);
            }
        }
        return dataDir;
    }

    private void createCacheEntryAsync(final String bookFile) {
        new AsyncTask<String, Integer, Void>(){

            @Override
            protected Void doInBackground(String... params) {
                createCacheEntry(params[0]);
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                notifyChanged();
            }
        }.execute(bookFile);

    }

    private void createCacheEntry(String bookFile) {
        EpubReader epubReader = new EpubReader();
        try {
            Book book = epubReader.readEpubLazy(bookFile, "UTF-8");
            BookData cache = new BookData(book.getMetadata().getFirstTitle(), book.getMetadata().getAuthors(), book.getCoverImage());
            saveBookData(bookFile, cache);
            this.cache.put(bookFile, cache);
        } catch (IOException e) {

        }
    }

    public void saveBookData(String bookFile, BookData cache) throws IOException {
        File cacheDir = getDataDir();
        File cacheFile = new File(cacheDir, bookFile.replace(File.separatorChar, '_') + ".cache");
        File bitmapFile = new File(cacheDir, bookFile.replace(File.separatorChar, '_') + ".cover");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(cacheFile));
        try {
            objectOutputStream.writeObject(cache);
        }
        finally {
            objectOutputStream.close();
        }
        Bitmap bitmap = cache.getThumbnail();
        if(bitmap != null){
            bitmap.compress(Bitmap.CompressFormat.PNG, 90, new FileOutputStream(bitmapFile));
        }
    }

    public void clearCache() {
        File externalCacheDir = getDataDir();
        File[] list = externalCacheDir.listFiles();
        if(list != null){
            for (File file : list) {
                file.delete();
            }
        }
        cache.clear();
        notifyInvalidated();
    }
}
